package com.wyfx.blockchain.service.impl;

import com.alibaba.fastjson.JSON;
import com.wyfx.blockchain.common.Constants;
import com.wyfx.blockchain.common.response.AliveNodeVo;
import com.wyfx.blockchain.common.response.MyResponseEntity;
import com.wyfx.blockchain.common.response.ResultResponse;
import com.wyfx.blockchain.config.PackEvent;
import com.wyfx.blockchain.dao.RocksDao;
import com.wyfx.blockchain.entity.Block;
import com.wyfx.blockchain.entity.BlockChain;
import com.wyfx.blockchain.entity.dto.IpAndPort;
import com.wyfx.blockchain.entity.dto.KeysAndIndex;
import com.wyfx.blockchain.service.IBlockService;
import com.wyfx.blockchain.utils.CommonUtils;
import com.wyfx.blockchain.utils.CryptoUtil;
import com.wyfx.blockchain.utils.FastJsonUtil;
import lombok.extern.log4j.Log4j2;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.ApplicationContext;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;

import javax.annotation.PostConstruct;
import java.util.*;

/**
 * @Description
 * @Auther: wsm
 * @Date: 2020/9/3 14:38
 */
@Service
@Log4j2
public class BlockServiceImp implements IBlockService {

    @Autowired
    private RocksDao rocksDao;

    /**
     * 创世块
     */
    @PostConstruct
    public void init() {
        Optional optional = rocksDao.find(Constants.KEY_OF_FIRST_BLOCK);
        //如果存在创世块就不再创建
        if (optional.isPresent()) {
            return;
        }
        Block block = new Block(0, CryptoUtil.SHA256("x0"), System.currentTimeMillis(), "null", "null");
        String hash = block.getHash();
        //保存数据与hash的映射关系  无需再维护与上一块的关系
        rocksDao.save(hash, FastJsonUtil.toJSONString(block));

        //设置第一个block的key value 保存数据到rocksDB 创世块生成时即是第一块也是最后一块
        rocksDao.save(Constants.KEY_OF_FIRST_BLOCK, hash);

        //设置最后一个block的key value  每次新增区块时维护一下最后一个区块的映射关系
        rocksDao.save(Constants.KEY_OF_LAST_BLOCK, hash);


    }


    /**
     * @param data
     * @desc 生成区块
     * @Return java.lang.String  成功返回 区块hash  失败返回null
     * @Author wsm
     * @date 2020/9/15 17:37
     */
    @Override
    public String generateBlock(String data) {
        //获取链上的最后一个区块数据
        Block lastBlock = getLastBlock();
        String previousHash = lastBlock.getHash();
        Integer index = lastBlock.getIndex() + 1;
        Long time = System.currentTimeMillis();
        String dataStr = data;
        String hash = CryptoUtil.SHA256(data + System.currentTimeMillis() + index);
        //设置上一个区块哈希到当前的数据中
        Block block = new Block(index, hash, time, dataStr, previousHash);
        //将新生成的区块添加到链上
        boolean b = addBlockToChain(block);
        return b == true ? block.getHash() : null;
    }

    /**
     * @param block
     * @desc 添加区块数据到链上
     * @Return void
     * @Author wsm
     * @date 2020/9/7
     */
    public synchronized boolean addBlockToChain(Block block) {

        try {
            String hash = block.getHash();
            String previousHash = block.getPreviousHash();
            //如果没有上一区块，说明该块就是创世块
            if (previousHash == null) {
                rocksDao.save(Constants.KEY_OF_FIRST_BLOCK, hash);
            } else {
                //保存上一区块对该区块的key value映射  当需要查询下一区块的hash时通过key= key_next_+当前区块hash 查询
                rocksDao.save(Constants.KEY_BLOCK_NEXT_PREFIX + block.getPreviousHash(), hash);
            }
            //存入rocksDB  维护hash 与区块数据的关系 通过hash可以查询到区块的信息
            rocksDao.save(hash, FastJsonUtil.toJSONString(block));
            //设置最后一个block的key value
            rocksDao.save(Constants.KEY_OF_LAST_BLOCK, hash);
        } catch (Exception e) {
            log.error("上链失败{}", e.getMessage());
            return false;
        }
        //通知各子节点来主节点同步区块数据
        syncBlockChain();
        return true;

    }

    //管理端ip和端口
    @Value("${manager.ip}")
    private String managerIp;
    @Value("${manager.port}")
    private Integer managerPort;
    @Value("${group.id}")
    private String groupId;


    /**
     * @param
     * @desc 通知所有子节点来主节点同步区块链数据  采用异步的方式去同步 并指定异步执行的线程池
     * @Return void
     * @Author wsm
     * @date 2020/9/8 16:38
     */
    @Async("taskExecutor")
    public void syncBlockChain() {

        log.info("Async Thread Name:{}", Thread.currentThread().getName());

        // 通过groupId获取group下所有节点信息
        String managerUrl = CommonUtils.generateUrl(managerIp, managerPort, Constants.GET_ALL_NODE_INFO);
        MyResponseEntity myResponseEntity = restTemplate.getForObject(managerUrl, MyResponseEntity.class, groupId);
        String data = myResponseEntity.getData().toString();
        AliveNodeVo aliveNodeVo = JSON.parseObject(data, AliveNodeVo.class);
        log.info("aliveNodeVo ={}", aliveNodeVo);
        String masterAddress = aliveNodeVo.getMasterAddress();
        Map map = parseMasterAddress(masterAddress);

        //  向group下所有子节点发起同步请求
//        IpAndPort ipAndPort = new IpAndPort("127.0.0.1", "8081");
        IpAndPort ipAndPort = new IpAndPort(map.get("ip").toString(), map.get("port").toString());

        //restTemplate 发送json请求
        HttpHeaders headers = new HttpHeaders();
        MediaType type = MediaType.parseMediaType("application/json; charset=UTF-8");
        headers.setContentType(type);
        headers.add("Accept", MediaType.APPLICATION_JSON.toString());
        HttpEntity<String> formEntity = new HttpEntity<String>(JSON.toJSONString(ipAndPort), headers);

        //  向子节点发起同步请求
        List<String> slaveList = aliveNodeVo.getSlaveList();
        String method = "/block/sync";
        for (String s : slaveList) {
//            String url = CommonUtils.generateUrl("127.0.0.1", 8080, "block/sync");
            ResultResponse response = restTemplate.postForObject(s + method, formEntity, ResultResponse.class);
            log.info("sync response :{}", response);
        }


    }


    private Map parseMasterAddress(String masterAddress) {
        //{"masterAddress":"http://192.168.137.1:8080","slaveList":["http://192.168.137.1:8081"]}
        String ip = masterAddress.substring(masterAddress.lastIndexOf("/") + 1, masterAddress.lastIndexOf(":"));
        Map map = new HashMap(6);
        String port = masterAddress.substring(masterAddress.lastIndexOf(":") + 1);
        map.put("ip", ip);
        map.put("port", port);
        return map;
    }

    @Override
    public Block getBlockByHash(String hash) {
        Optional optional = rocksDao.find(hash);
        return !optional.isPresent() ? null : FastJsonUtil.toBean(optional.get().toString(), Block.class);
    }

    @Override
    public Block getFirstBlock() {
        //获取创世块的hash
        Optional optional = rocksDao.find(Constants.KEY_OF_FIRST_BLOCK);
        String firstBlockHash = optional.isPresent() ? optional.get().toString() : "";
        //获取块数据
        Block blockByHash = getBlockByHash(firstBlockHash);
        return blockByHash;
    }

    @Override
    public Block getLastBlock() {
        //获取最后一个区块的hash
        Optional optional = rocksDao.find(Constants.KEY_OF_LAST_BLOCK);
        String lastBlockHash = optional.isPresent() ? optional.get().toString() : "";
        return getBlockByHash(lastBlockHash);
    }

    @Override
    public Block getNextBlock(Block block) {
        if (block == null || "".equals(block.getHash())) {
            return getFirstBlock();
        }
        Optional optional = rocksDao.find(Constants.KEY_BLOCK_NEXT_PREFIX + block.getHash());
        String nextHash = optional.isPresent() ? optional.get().toString() : "";
        if (nextHash == null || "".equals(nextHash)) {
            return null;
        }
        return getBlockByHash(nextHash);

    }


    /**
     * 一次性查询所有的区块
     *
     * @return 所有区块
     */
    @Override
    public List<Block> listOfBlock() {
        // 创建一个返回的集合
        List<Block> allBlock = new ArrayList<>();
        // 先获取第一个创世区块
        Block firstBlock = getFirstBlock();
        // 将创世区块加入到
        if (firstBlock != null) {
            allBlock.add(firstBlock);
        }
        // 设置当前区块
        Block currentBlock = firstBlock;
        while (true) {
            Block nextBlock = getNextBlock(currentBlock);
            if (null == nextBlock || nextBlock.equals(firstBlock)) {
                break;
            }
            if (nextBlock != null) {
                allBlock.add(nextBlock);
            }
            currentBlock = nextBlock;
        }
        return allBlock;
    }


    private BlockChain blockChain = new BlockChain();

    public Block getNextBlockByHash(String hash) {
        if (hash == null || "".equals(hash)) {
            return getFirstBlock();
        }
        Optional optional = rocksDao.find(Constants.KEY_BLOCK_NEXT_PREFIX + hash);
        String nextHash = optional.isPresent() ? optional.get().toString() : "";
        if (nextHash == null || "".equals(nextHash)) {
            return null;
        }
        return getBlockByHash(nextHash);
    }

    @Autowired
    private RestTemplate restTemplate;

    /*===================导入农业数据========================*/


    /**
     * 注入容器
     */
    @Autowired
    ApplicationContext applicationContext;

    /**
     * @param data
     * @desc 添加农业数据
     * @Return void
     * @Author wsm
     * @date 2020/9/7 21:24
     */
    @Override
    public void addData(String data) {

        //保存每次储存数的key集合
        List<String> keys = new ArrayList<>();
        //每条添加数据的下标
        Integer index = 0;
        //查询上次保存的keys和index
        Optional optional = rocksDao.find(Constants.KEYS_AND_INDEXS);
        String keysAndIndex = optional.isPresent() ? optional.get().toString() : null;
        if (keysAndIndex != null) { //不是第一次保存数据需要从rocksDB中恢复keys和index并重新赋值
            KeysAndIndex kAndI = JSON.parseObject(keysAndIndex, KeysAndIndex.class);
            //恢复rocksDB中的数据
            keys = kAndI.getKeys();
            index = kAndI.getIndex();
        }
        //第一次直接保存数据
        ++index;
        //每条数据的下标
        String dataKey = "index_" + index;
        //保存每条数据
//        rocksDao.save(dataKey, JSON.toJSONString(data));
        rocksDao.save(dataKey, data);
        //保存下标和所有的key到rocksDB
        KeysAndIndex saveKeyAndIndex = new KeysAndIndex();
        saveKeyAndIndex.setIndex(index);
        keys.add(dataKey);
        saveKeyAndIndex.setKeys(keys);
        rocksDao.save(Constants.KEYS_AND_INDEXS, JSON.toJSONString(saveKeyAndIndex));

        // todo 通过容器发布打包数据事件
        applicationContext.publishEvent(new PackEvent(""));


    }

    /**
     * @desc 打包数据并发送到主节点制作成区块
     * @Return java.util.List<java.lang.String>
     * @Author wsm
     * @date 2020/9/7 21:45
     */
    @Override
    public Map<String, List<String>> packageData() {
        //获取本次需要打包的数据keys
        Optional optional = rocksDao.find(Constants.KEYS_AND_INDEXS);
        String s = optional.isPresent() ? optional.get().toString() : null;
        if (Objects.isNull(s)) {
            return null;
        }
        KeysAndIndex keysAndIndex = JSON.parseObject(s, KeysAndIndex.class);
        log.info("KeysAndIndex {}", keysAndIndex);
        //通过Keys 查询相应的数据
        List<String> dataList = new ArrayList<>();
        //keyList
        List<String> keyList = new ArrayList<>();
        List<String> keys = keysAndIndex.getKeys();
        for (String key : keys) {
            keyList.add(key);
            String data = findDataByKey(key);
            if (data != null) {
                dataList.add(data);
            }
        }

        Map map = new HashMap(6);
        map.put("dataList", dataList);
        map.put("keyList", keyList);

        //返回打包数据 并发送给主节点生成区块
        return map;

    }

    private String findDataByKey(String key) {
        Optional optional = rocksDao.find(key);
        String data = optional.isPresent() ? optional.get().toString() : null;
        return data;
    }


}
